package com.maowu.pooling;

import java.util.ArrayList;
import java.util.WeakHashMap;

public class SimplePool<C>
{
    private ArrayList<C> pool = new ArrayList<C>();
    private ArrayList<WaitingTicket> waitingList = new ArrayList<WaitingTicket>();
    
    private Object mutex = new Object();
    private Object nothing = new Object();
    
    private WeakHashMap<C, Object> borrowedMap = new WeakHashMap<C, Object>();
    private WeakHashMap<CreateConnecionToken, Object> handlingMap = new WeakHashMap<CreateConnecionToken, Object>();
    
    private ConnectionFactory factory;
    
    private int maxConnection = 10;
    private long waitingTimeout = 10000;
    private long connectionTimeout = 10000;
    
    private boolean destroyed = false;
    
    public int getLent()
    {
        synchronized (mutex)
        {
            return borrowedMap.size() + handlingMap.size();
        }
    }

    public SimplePool(ConnectionFactory factory)
    {
        this(factory, 20, 10000, 10000);
    }


    public SimplePool(ConnectionFactory factory, int maxConnection, long waitingTimeout, long connectionTimeout)
    {
        super();
        this.factory = factory;
        this.maxConnection = maxConnection;
        this.waitingTimeout = waitingTimeout;
        this.connectionTimeout = connectionTimeout;
    }
    
    
    public long getConnectionTimeout()
    {
        return connectionTimeout;
    }


    public void setConnectionTimeout(long connectionTimeout)
    {
        this.connectionTimeout = connectionTimeout;
    }


    public ConnectionFactory getFactory()
    {
        return factory;
    }


    public void setFactory(ConnectionFactory factory)
    {
        this.factory = factory;
    }


    public int getMaxConnection()
    {
        return maxConnection;
    }


    public void setMaxConnection(int maxConnection)
    {
        this.maxConnection = maxConnection;
    }


    public long getWaitingTimeout()
    {
        return waitingTimeout;
    }


    public void setWaitingTimeout(long waitingTimeout)
    {
        this.waitingTimeout = waitingTimeout;
    }



    public C borrowConnection() throws FailedToConnectException, WaitingTimeoutException, PoolErrorException
    {
        WaitingTicket ticket = null;
        CreateConnecionToken token = null;
        C conn = null;
        try
        {
            synchronized (mutex)
            {
                if (destroyed)
                {
                    throw new IllegalStateException("The pool has been destroyed.");
                }
                if (borrowedMap.size() + handlingMap.size() >= maxConnection)
                {
                    //too many connections, get a waiting ticket and wait.
                    ticket = new WaitingTicket();
                    waitingList.add(ticket);
                }
                else 
                {
                    //just get from pool or create a new one.
                    if (pool.isEmpty())
                    {
                        token = new CreateConnecionToken();
                        handlingMap.put(token, nothing);
                    }
                    else 
                    {
                        conn =  pool.remove(0);
                        borrowedMap.put(conn, nothing);
                    }
                }
            }
            
            if (ticket != null)
            {
                synchronized (ticket)
                {
                    if (ticket.conn != null)
                    {
                        //luckily, no need to wait.
                        conn = ticket.conn;
                    }
                    else 
                    {
                        try
                        {
                            ticket.wait(waitingTimeout);
                            if (ticket.conn != null)
                            {
                                conn = ticket.conn;
                            }
                            else
                            {
                                ticket.gaveUp = true;
                                throw new WaitingTimeoutException();
                            }
                        }
                        catch (InterruptedException e)
                        {
                            ticket.gaveUp = true;
                            throw new WaitingTimeoutException("Waiting thread is interrupted.", e);
                        }
                    }
                }
            }
            else if (token != null)
            {
                try
                {
                    conn = (C) factory.createConnection(connectionTimeout);
                    synchronized (mutex)
                    {
                        handlingMap.remove(token);
                        if (conn == null)
                        {
                            throw new PoolErrorException("ConnectionFactory return null at createConnection() method. Please check the ConnectionFactory implementation.");
                        }
                        else
                        {
                            borrowedMap.put(conn, nothing);
                        }
                    }
                }
                catch (FailedToConnectException e)
                {
                    synchronized (mutex)
                    {
                        handlingMap.remove(token);
                    }
                    throw e;
                }

            }
            
            if (conn != null)
            {
                try
                {
                    factory.touchConnection(conn, connectionTimeout);
                }
                catch (ConnectionDamagedException e)
                {
                    e.printStackTrace();
                    //the connection is damaged, get a new one.
                    factory.destroyConnection(conn);
                    C newConn = (C) factory.createConnection(connectionTimeout);
                    synchronized (mutex)
                    {
                        borrowedMap.remove(conn);

                        if (newConn == null)
                        {
                            throw new PoolErrorException("ConnectionFactory return null at createConnection() method. Please check the ConnectionFactory implementation.");
                        }
                        else
                        {
                            borrowedMap.put(newConn, nothing);
                        }
                        conn = newConn;
                    }
                }
                catch (FailedToConnectException e)
                {
                    synchronized (mutex)
                    {
                        borrowedMap.remove(conn);
                    }
                    throw e;
                }
                return conn;
            }
            else 
            {
                throw new PoolErrorException("Sorry, must be pool's coding bug, any way, not able to get a connection.");
            }
            
        }
        catch (RuntimeException e)
        {
            synchronized (mutex)
            {
                if (conn != null)
                {
                    factory.destroyConnection(conn);
                    borrowedMap.remove(conn);
                }
                if (token != null)
                {
                    handlingMap.remove(token);
                }
                if (ticket != null)
                {
                    ticket.gaveUp = true;
                }
            }
            throw new PoolErrorException("RuntimeException has been caught.", e);
        }
   }
    
    public void connectionBroken(C conn)
    {
        synchronized (mutex)
        {
            borrowedMap.remove(conn);
            factory.destroyConnection(conn);
        }
    }
    
    
    public void returnConnection(C conn) throws PoolErrorException
    {
        WaitingTicket ticket = null;
        try
        {
            synchronized (mutex)
            {
                if (destroyed || (handlingMap.size() + borrowedMap.size()) > maxConnection)
                {
                    //too many connection, disconnect this one
                    borrowedMap.remove(conn);
                    factory.destroyConnection(conn);
                }
                else 
                {
                    while (!waitingList.isEmpty() && ticket == null)
                    {
                        ticket = waitingList.remove(0);
                        synchronized (ticket)
                        {
                            if (ticket.gaveUp)
                            {
                                //the waiting guy has given up. try next waiting gug.
                                ticket = null;
                            }
                            else 
                            {
                                ticket.conn = conn;
                                ticket.notifyAll();// wake up the waiting guy to get the connection.
                            }
                        }
                    }
                    
                    if (ticket == null)
                    {
                        borrowedMap.remove(conn);
                        pool.add(conn);
                    }
                }
            }
        }
        catch (RuntimeException e)
        {
            synchronized (mutex)
            {
                if (conn != null)
                {
                    borrowedMap.remove(conn);
                }
            }
            throw new PoolErrorException("RuntimeException has been caught.", e);
        }       
    }
    
    public void dismissWaitlist()
    {
        ArrayList<WaitingTicket> list = new ArrayList<WaitingTicket>();
        synchronized (mutex)
        {
            list.addAll(waitingList);
            waitingList.clear();

        }
        
        for (WaitingTicket ticket : list)
        {
            synchronized (ticket)
            {
                ticket.notifyAll();
            }
        }
    }
    
    public void close()
    {
        synchronized (mutex)
        {
            destroyed = true;
            dismissWaitlist();
        }

    }
    
    private class CreateConnecionToken
    {
        
    }
    private class WaitingTicket
    {
        private C conn;
        private boolean gaveUp;
    }
    public boolean isClosed()
    {
        return destroyed;
    }
}
